﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Reflection;
using System.Text;

using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Security;

using Behemoth.SharePoint;
using Behemoth.SharePoint.CustomFieldTypes;

namespace Behemoth.SharePoint.LoggedCommentField
{
    // TODO: Replace, as needed, "SPFieldText" with some other class derived from SPField. 
    // TODO: Update, as needed, ParentType element in fldtypes*.xml in this solution. 
    [CLSCompliant(false)]
    [Guid("96b62e6e-337c-45f3-8fb3-7446c8576fdf")]
    public class LoggedCommentField : SPFieldMultiLineText {

        public LoggedCommentField(SPFieldCollection fields, string fieldName)
            : base(fields, fieldName)
        {
          Init();
        }
        
        public LoggedCommentField(SPFieldCollection fields, string typeName, string displayName)
            : base(fields, typeName, displayName)
        {
          Init();
        }

      private void Init() {
        CustomFieldPropertyDisplayWorkaround.DoHookup(this);
        SPPresenceTools.AddFixPresenceScript();
        this.AllowHyperlink = true;
        this.RichText = true;
        this.SetRichTextMode(SPRichTextMode.FullHtml);
      }

        public override BaseFieldControl FieldRenderingControl {
          [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
          get {
            BaseFieldControl fieldControl;
            if (this.EditRichText) {
              fieldControl = new LoggedComment_RichTextField();
            } else {
              fieldControl = new LoggedComment_NoteField();
            }
            fieldControl.FieldName = this.InternalName;
            return fieldControl;
          }
        }

        #region Custom Properties

      protected ImportCommentsStatus ImportStatus {
        get {
          object o = this.GetCustomProperty("ImportCommentsStatus");
          if (o == null || string.IsNullOrEmpty(o.ToString()))
            return ImportCommentsStatus.NotStarted;
          return (ImportCommentsStatus)Enum.Parse(typeof(ImportCommentsStatus), o.ToString(), true);
        }
        set {
          this.SetCustomProperty("ImportCommentsStatus", value.ToString());
        }
      }

      public string ImportCommentsField {
        get {
          if (this.GetCustomProperty("ImportCommentsField") == null)
            return string.Empty;
          return this.GetCustomProperty("ImportCommentsField").ToString();
        }
        set {
          this.SetCustomProperty("ImportCommentsField", value);
        }
      }
      
      public string RichTextMode2 {
        get {
          if (this.GetCustomProperty("RichTextMode2") == null)
            return string.Empty;
          return this.GetCustomProperty("RichTextMode2").ToString();
        }
        set {
          this.SetCustomProperty("RichTextMode2", value);
        }
      }

      public string EditRichTextMode {
        get {
          if (this.GetCustomProperty("EditRichTextMode") == null)
            return string.Empty;
          return this.GetCustomProperty("EditRichTextMode").ToString();
        }
        set {
          this.SetCustomProperty("EditRichTextMode", value);
        }
      }

      public bool DebugMode {
        get {
          if (this.GetCustomProperty("DebugMode") == null)
            return false;
          return (bool)this.GetCustomProperty("DebugMode");
        }
        set {
          this.SetCustomProperty("DebugMode", value);
        }
      }

      public bool EditRichText {
        get {
          if (this.GetCustomProperty("EditRichText") == null)
            return false;
          return (bool)this.GetCustomProperty("EditRichText");
        }
        set {
          this.SetCustomProperty("EditRichText", value);
        }
      }

      public CommentHistoryOrder HistoryDisplaySortOrder {
        get {
          object o = this.GetCustomProperty("HistoryDisplaySortOrder");
          return o == null ? CommentHistoryOrder.RecentMessagesAtTop: ParseCommentHistoryOrder(o.ToString());
        }
        set {
          this.SetCustomProperty("HistoryDisplaySortOrder", value);
        }
      }
      // addresses a "bug" where chocies are not stored using the Value attribute of the choice -- boo! SharePoint! boo!
      private CommentHistoryOrder ParseCommentHistoryOrder(string value) {
        value = value.Replace(" ", string.Empty); // strip out spaces
        return (CommentHistoryOrder)Enum.Parse(typeof(CommentHistoryOrder), value, true);
      }

      public CommentHistoryPosition HistoryDisplayPosition {
        get {
          object o = this.GetCustomProperty("HistoryDisplayPosition");
          return o == null ? CommentHistoryPosition.None : ParseCommentHistoryPosition(o.ToString());
        }
        set {
          this.SetCustomProperty("HistoryDisplayPosition", value);
        }
      }
      // addresses a "bug" where chocies are not stored using the Value attribute of the choice -- boo! SharePoint! boo!
      private CommentHistoryPosition ParseCommentHistoryPosition(string value) {
        value = value.Replace(" ", string.Empty); // strip out spaces
        return (CommentHistoryPosition)Enum.Parse(typeof(CommentHistoryPosition), value, true);
      }

      public PresenceIconPlacementChoice PresenceIconPlacement {
        get {
          object o = this.GetCustomProperty("PresenceIconPlacement");
          return o == null ? PresenceIconPlacementChoice.BeforeLink : ParsePresenceIconPlacementChoice(o.ToString());
        }
        set {
          string s = (value == PresenceIconPlacementChoice.BeforeLink) ? "Before Link" : "After Link";
          this.SetCustomProperty("PresenceIconPlacement", s);
        }
      }
      private PresenceIconPlacementChoice ParsePresenceIconPlacementChoice(string value) {
        value = value.Replace(" ", string.Empty); // strip out spaces
        return (PresenceIconPlacementChoice)Enum.Parse(typeof(PresenceIconPlacementChoice), value, true);
      }

      public string HeaderFormatRichText {
        get {
          object o = this.GetCustomProperty("HeaderFormatRichText");
          string s = o == null ? string.Empty : o.ToString();
          return string.IsNullOrEmpty(s) ? "<div class=\"LoggedCommentHistoryHeader\" style=\"font-weight: bold\">Comment by {0} at {1}</div>" : s;
        }
        set { this.SetCustomProperty("HeaderFormatRichText", value); }
      }

      /*
      public string HeaderFormatPlainText {
        get {
          object o = this.GetCustomProperty("HeaderFormatPlainText");
          string s = o == null ? string.Empty : o.ToString();
          return string.IsNullOrEmpty(s) ? "Comment by {0} at {1}\r\n" : s;
        }
        set { this.SetCustomProperty("HeaderFormatPlainText", value); }
      }
       */

      /// <summary>
      /// Here we have extended GetProperty, which usually only returns
      /// OOTB properties and attributes of the Field element in XML.
      /// It will now return custom properties as well.
      /// </summary>
      /// <param name="propertyName"></param>
      /// <returns></returns>
      public override string GetProperty(string propertyName) {
        string s = base.GetProperty(propertyName);
        if (!string.IsNullOrEmpty(s))
          return s;
        if (this.GetCustomProperty(propertyName) == null)
          return string.Empty;
        return this.GetCustomProperty(propertyName).ToString();
      }
      
      public override SPRichTextMode RichTextMode {
        get {
          return base.RichTextMode;
        }
        set {
          base.RichTextMode = value;
        }
      }

#endregion

      public override Type FieldValueType {
        get { return typeof(LoggedCommentFieldValue); }
      }
      public override string GetFieldValueAsHtml(object value) {
        //return base.GetFieldValueAsHtml( value );
        LoggedCommentFieldValue typedValue = LoggedCommentFieldValue.UpConvertMultiColumnValue(value);
        if (typedValue == null)
          throw new ArgumentException("Parameter must be of type LoggedCommentFieldValue.", "value");
        // TODO ensure this is the value we really want
        string html = typedValue.DisplayValueFull;
        return html;
      }
      public override string GetFieldValueAsText(object value) {
        //return base.GetFieldValueAsText( value );
        LoggedCommentFieldValue typedValue = LoggedCommentFieldValue.UpConvertMultiColumnValue(value);
        if (typedValue == null)
          throw new ArgumentException("Parameter must be of type LoggedCommentFieldValue.", "value");
        // TODO ensure this is the value we really want
        string html = typedValue.DisplayValueFull;
        return html;
      }
      /// <summary>
      /// Takes delimited string values and converts them to a LoggedCommentFieldValue object
      /// which is based on the SPFieldMultiColumnValue and XmlFieldValue classes.
      /// </summary>
      /// <param name="value"></param>
      /// <returns></returns>
      public override object GetFieldValue(string value) {
        object result = base.GetFieldValue(value);
        LoggedCommentFieldValue typedValue = LoggedCommentFieldValue.UpConvertMultiColumnValue(value);
        if (typedValue != null)
          typedValue.PassFieldProperties(this);
        return typedValue;
      }
      public override string GetFieldValueForEdit(object value) {
        LoggedCommentFieldValue typedValue = LoggedCommentFieldValue.UpConvertMultiColumnValue(value);
        if (typedValue != null)
          typedValue.PassFieldProperties(this);
        string result = base.GetFieldValueForEdit(typedValue);
        // note that comments are append only...
        return string.Empty; 
      }
      public override string GetValidatedString(object value) {
        LoggedCommentFieldValue typedValue = LoggedCommentFieldValue.UpConvertMultiColumnValue(value);
        if (this.Required && typedValue.DataIsEmpty)
          throw new SPFieldValidationException(string.Format("You must specify a value for {0}.", this.Title));
        if (!typedValue.IsValid()) {
          StringBuilder sb = new StringBuilder();
          foreach (string vmsg in typedValue.ValidationMessages)
            sb.Append(vmsg);
          throw new SPFieldValidationException(
            string.Format(sb.ToString(),
            this.Title, "[No Data]"
          ));
        }
        return base.GetValidatedString(value);
      }

      public void UpdateCommentsCamlText() {
        SPList list = this.ParentList;
        if (list == null)
          return;
        foreach (SPListItem item in list.Items) {
          UpdateCommentsCamlText(item);
        }
      }
      private void UpdateCommentsCamlText(SPListItem item) {
        if (item[this.Title] == null)
          return;
        bool updates = item.Web.AllowUnsafeUpdates;
        item.Web.AllowUnsafeUpdates = true;
        LoggedCommentFieldValue fv = new LoggedCommentFieldValue(item[this.Title].ToString());
        fv.PassFieldProperties(this);
        fv.SyncDisplayValues();
        item[this.Title] = fv;
        item.Update();
        item.Web.AllowUnsafeUpdates = updates;
      }

      #region Import Versioned Comments

      public void ImportStandardComments(bool enableFlagging) {
        SPList list = this.ParentList;
        if (list == null || !list.EnableVersioning)
          return;
        string legacyCommentFieldName = ImportCommentsField;
        if (string.IsNullOrEmpty(legacyCommentFieldName))
          return;
        string fieldTitle = string.Empty;
        try {
          SPField field = SPFieldTools.TryGetField(list.Fields, legacyCommentFieldName, false);
          fieldTitle = field.Title;
          if (field == null)
            return;
          /*
          SPFieldMultiLineText textField = fieldName as SPFieldMultiLineText;
          if (textField == null)
            return;
           */
        } catch {
          return;
        }
          
        if (ImportStatus != ImportCommentsStatus.NotStarted)
          return;
        if (enableFlagging)
          SetImportStatus(list, ImportCommentsStatus.InProgress);
        foreach (SPListItem item in list.Items) {
          ImportVersions(item, fieldTitle);
        } // foreach
        if (enableFlagging)
          SetImportStatus(list, ImportCommentsStatus.Done);
      }

      private void ImportVersions(SPListItem item, string fieldTitle) {
        // skip this item as it's target comments fieldName is not empty;
        LoggedCommentFieldValue fv = item[this.Title] as LoggedCommentFieldValue;
        if (fv != null && !fv.IsCommentsEmpty)
          return;
        else
          fv = new LoggedCommentFieldValue(new Data.Comments(), this);

        SPListItemVersionCollection versions = item.Versions;
        //string commentsText = string.Empty;
        for (int i = versions.Count - 1; i >= 0; i--) {
          SPListItemVersion version = versions[i];

          object v = version[fieldTitle];
          if (v == null)
            continue;
          string comment = v.ToString();
          DateTime dtCreated = version.Created;
          fv.AddComment(version.CreatedBy, dtCreated.ToLocalTime(), false, comment); // version history is in GMT; setting UTC flag did not work, so I'm just passing the local time instead
          //commentsText = LoggedCommentFunctions.AppendNewComment(this, version.CreatedBy, dtCreated, commentsText, newText);  
        } // for
        // set the newly updated text
        SetComments(item, fv);
        //SetCommentText(item, commentsText);
      }

      private void SetComments(SPListItem item, LoggedCommentFieldValue fv) {
        bool updates = item.Web.AllowUnsafeUpdates;
        item.Web.AllowUnsafeUpdates = true;
        item[this.Title] = fv; // should this be fv.ToString(); ??
        item.Update();
        item.Web.AllowUnsafeUpdates = updates;
      }
      /*
      private void SetCommentText(SPListItem item, string commentsText) {
        bool updates = item.Web.AllowUnsafeUpdates;
        item.Web.AllowUnsafeUpdates = true;
        item[this.Title] = commentsText;
        item.Update();
        item.Web.AllowUnsafeUpdates = updates;
      }
       */

      private void SetImportStatus(SPList list, ImportCommentsStatus status) {
        bool updates = list.ParentWeb.AllowUnsafeUpdates;
        list.ParentWeb.AllowUnsafeUpdates = true;
        ImportStatus = status;
        this.Update();
        list.ParentWeb.AllowUnsafeUpdates = updates;
      }

      #endregion

      /*
      public string GenerateHeaderString(SPFieldUserValue user) {
        return LoggedCommentFunctions.GenerateHeaderString(this, user);
      }
      public string GenerateHeaderString(SPUser user) {
        return LoggedCommentFunctions.GenerateHeaderString(this, user);
      }
      public string GenerateHeaderString() {
        return LoggedCommentFunctions.GenerateHeaderString(this);
      }
       */

      public override void Update() {
        SyncRichTextMode();

        SPFieldTools.SetFieldProperty(this, "DebugMode", this.DebugMode);
        SPFieldTools.SetFieldProperty(this, "EditRichText", this.EditRichText);
        SPFieldTools.SetFieldProperty(this, "EditRichTextMode", this.EditRichTextMode);
        SPFieldTools.SetFieldProperty(this, "HistoryDisplayPosition", this.HistoryDisplayPosition.ToString());
        SPFieldTools.SetFieldProperty(this, "HistoryDisplaySortOrder", this.HistoryDisplaySortOrder.ToString());
        SPFieldTools.SetFieldProperty(this, "PresenceIconPlacement", this.PresenceIconPlacement.ToString());
        SPFieldTools.SetFieldProperty(this, "RichTextMode2", this.RichTextMode2);
        SPFieldTools.SetFieldProperty(this, "HeaderFormatRichText", this.HeaderFormatRichText);
        //SPFieldTools.SetFieldProperty(this, "HeaderFormatPlainText", this.HeaderFormatPlainText);
        SPFieldTools.SetFieldProperty(this, "AllowHyperlink", this.AllowHyperlink);
        SPFieldTools.SetFieldProperty(this, "NumberOfLines", this.NumberOfLines);

        // this updates any CAMl so the fields will reflect changes made in the 
        this.UpdateCommentsCamlText();

        // this attempts to import new data into the current fieldName from the legacy fieldName information
        this.ImportStatus = ImportCommentsStatus.NotStarted;
        this.ImportStandardComments(false);
        this.ImportStatus = ImportCommentsStatus.Done;

        base.Update();

        // HACK don't know if this will help, but we're going to try it
        base.Update();
      }   

      private void SyncRichTextMode() {
        string s = this.RichTextMode2;
        switch(s) {
          case "Plain text": 
            this.EditRichText = false;
            this.EditRichTextMode = SPRichTextMode.FullHtml.ToString();
            break;
          case "Rich text (Bold, italics, text alignment)": 
            this.EditRichText = true;
            this.EditRichTextMode = SPRichTextMode.Compatible.ToString();
            break;
          case "Enhanced rich text (Rich text with pictures, tables, and hyperlinks)": 
            this.EditRichText = true;
            this.EditRichTextMode = SPRichTextMode.FullHtml.ToString();
            break;
        }
      }

    } // class

  public enum CommentHistoryPosition {
    None = 0,
    Bottom = 1,
    Top = 2
  }

  public enum ImportCommentsStatus {
    NotStarted = 0,
    InProgress = 1,
    Done = 2
  }

  public enum CommentHistoryOrder {
    RecentMessagesAtTop = 0,
    RecentMessagesAtBottom = 1
  }

  public enum PresenceIconPlacementChoice {
    BeforeLink = 0,
    AfterLink = 1
  }

}
